import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.preprocessing import MinMaxScaler, MultiLabelBinarizer

# 加载数据
df = pd.read_csv("cleaned_tourism_data.csv")
df['Sites Visited'] = df['Sites Visited'].str.split(', ')  # 转换为列表

# 处理综合评分
df['Tourist Rating'] = pd.to_numeric(df['Tourist Rating'], errors='coerce').fillna(df['Tourist Rating'].mean())
df['Satisfaction'] = pd.to_numeric(df['Satisfaction'], errors='coerce').fillna(df['Satisfaction'].mean())
df['Normalized_Rating'] = (df['Tourist Rating'] - df['Tourist Rating'].min()) / (
            df['Tourist Rating'].max() - df['Tourist Rating'].min())
df['Normalized_Satisfaction'] = (df['Satisfaction'] - df['Satisfaction'].min()) / (
            df['Satisfaction'].max() - df['Satisfaction'].min())
df['Composite_Score'] = (0.5 * df['Normalized_Rating'] + 0.5 * df['Normalized_Satisfaction']) * 5

# 创建用户-景点评分矩阵
exploded_df = df.explode('Sites Visited')
ratings_df = exploded_df[['Tourist ID', 'Sites Visited', 'Composite_Score']]
user_counts = ratings_df['Tourist ID'].value_counts()
valid_users = user_counts[user_counts >= 2].index
filtered_ratings = ratings_df[ratings_df['Tourist ID'].isin(valid_users)]
user_item_matrix = filtered_ratings.pivot_table(
    index='Tourist ID',
    columns='Sites Visited',
    values='Composite_Score',
    aggfunc='mean',
    fill_value=0
)

# 提取用户特征
user_features = df.groupby('Tourist ID').agg({
    'Age': 'first',
    'Interests': 'first',
    'Preferred Tour Duration': 'first'
}).reset_index()

# 处理Interests的多热编码
user_features['Interests'] = user_features['Interests'].str.split(', ')
mlb = MultiLabelBinarizer()
interests_encoded = pd.DataFrame(
    mlb.fit_transform(user_features['Interests']),
    columns=mlb.classes_,
    index=user_features.index
)
user_features = pd.concat([user_features.drop(['Interests'], axis=1), interests_encoded], axis=1)

# 归一化Age和Duration
scaler = MinMaxScaler()
user_features[['Age_Norm', 'Duration_Norm']] = scaler.fit_transform(
    user_features[['Age', 'Preferred Tour Duration']]
)
user_features.drop(['Age', 'Preferred Tour Duration'], axis=1, inplace=True)

# 创建用户-景点访问的二进制矩阵
user_sites = exploded_df.groupby(['Tourist ID', 'Sites Visited']).size().unstack(fill_value=0).clip(upper=1)
user_features = user_features.merge(
    user_sites,
    left_on='Tourist ID',
    right_index=True,
    how='left'
).fillna(0)

# 设置索引
user_features.set_index('Tourist ID', inplace=True)

# 保存所有景点列名
site_columns = user_sites.columns.tolist()


# 准备新用户处理函数
def prepare_new_user(new_user_info, mlb, scaler, site_columns):
    """处理新用户特征并转换为模型需要的格式"""
    # 处理兴趣特征
    interests = new_user_info.get('Interests', '')
    if isinstance(interests, str):
        interests = interests.split(', ') if interests else []

    # 多热编码
    interests_encoded = mlb.transform([interests])
    interests_df = pd.DataFrame(interests_encoded, columns=mlb.classes_)

    # 归一化数值特征
    age = new_user_info.get('Age', 30)  # 默认值
    duration = new_user_info.get('Preferred Tour Duration', 5)  # 默认值
    age_duration = scaler.transform([[age, duration]])

    # 构建特征字典
    user_data = {
        'Age_Norm': age_duration[0][0],
        'Duration_Norm': age_duration[0][1]
    }

    # 添加兴趣特征
    for interest in interests_df.columns:
        user_data[interest] = interests_df[interest].iloc[0]

    # 添加访问历史特征
    visited_sites = new_user_info.get('Sites Visited', [])
    for site in site_columns:
        user_data[site] = 1 if site in visited_sites else 0

    # 创建DataFrame并确保列对齐
    return pd.DataFrame([user_data]).reindex(columns=user_features.columns, fill_value=0)


# 推荐函数
def get_recommendations_for_new_user(new_user_info, n=5, k=3):
    """为新用户生成景点推荐"""
    # 准备新用户特征
    new_user_df = prepare_new_user(new_user_info, mlb, scaler, site_columns)

    # 计算相似度
    combined_users = pd.concat([user_features, new_user_df])
    similarity_matrix = cosine_similarity(combined_users)
    new_user_similarities = pd.Series(
        similarity_matrix[-1, :-1],  # 排除最后一行（新用户自己）
        index=user_features.index
    )

    # 关键修改：确保相似用户在user_item_matrix中存在
    # 先获取所有可能的相似用户
    possible_similar_users = new_user_similarities.nlargest(k * 2)  # 取两倍数量作为候选

    # 筛选实际存在于user_item_matrix中的用户
    valid_similar_users = possible_similar_users[
        possible_similar_users.index.isin(user_item_matrix.index)
    ].nlargest(k)

    # 如果没有有效用户则返回空
    if len(valid_similar_users) == 0:
        return []

    # 获取已访问景点
    visited = set(new_user_info.get('Sites Visited', []))

    # 生成推荐
    recommendations = []
    for site in user_item_matrix.columns:
        if site not in visited:
            try:
                # 获取有效相似用户的评分
                relevant_ratings = user_item_matrix.loc[valid_similar_users.index, site]
                weighted_sum = (valid_similar_users.values * relevant_ratings).sum()
                sum_weights = valid_similar_users.sum()
                pred_score = weighted_sum / sum_weights if sum_weights != 0 else 0
                recommendations.append((site, pred_score))
            except KeyError:
                # 处理个别不存在的景点（如果有）
                continue

    # 返回Top-N推荐
    return sorted(recommendations, key=lambda x: x[1], reverse=True)[:n]

# 示例使用
if __name__ == "__main__":
    # 示例新用户数据
    new_user = {
        'Age': 30,
        'Interests': 'history, culture',
        'Preferred Tour Duration': 5,
        'Sites Visited': ['Machu Picchu', 'Taj Mahal']  # 替换为实际存在的景点
    }

    # 生成推荐
    recommendations = get_recommendations_for_new_user(new_user)
    print("Top 5 Recommended Sites:")
    for i, (site, score) in enumerate(recommendations, 1):
        print(f"{i}. {site} (Predicted Score: {score:.2f})")